package executor;

import logger.Logger;
import model.Board;
import model.Position;
import rule.RuleOperator;

/**
 * Created by qianyu.zm on 2017/3/7.
 */
public class GameExecutor {

    private RuleOperator ruleOperator;

    public GameExecutor(RuleOperator ruleOperator) {
        this.ruleOperator = ruleOperator;
    }


    private Board rawBoard, operationBoard;
    private int[] result;
    private int lineMax;

    public void setBoard(Board board) {
        this.rawBoard = board;
        this.operationBoard = board.clone();
        this.result = new int[board.longerSide];
        this.lineMax = (int) Math.pow(2, board.shorterSide);
    }

    public void calc(){
        int firstOperateX = 0;
        while (!calcLine(operationBoard, 0)){
            //reset operationBoard
            this.operationBoard = rawBoard.clone();
            firstOperateX++;
            if(firstOperateX >= lineMax){
                System.out.println("No answer!");
                return;
            }
            System.out.println("firstOperateX:" + Integer.toString(firstOperateX, 2));
            operate(operationBoard, firstOperateX, 0);
            printTable(operationBoard.table);
        }
        result[0] = firstOperateX;
        System.out.println("Congratulation!!Result:");
        printTable(result);
        System.out.println("Congratulation!!Table:");
        printTable(operationBoard.table);
    }

    private boolean calcLine(Board operationBoard, int lineIndex){
        boolean operateLastLine = lineIndex == operationBoard.longerSide - 2;
        int internalX = 0;
        Board board = operationBoard.clone();
        while (internalX < lineMax){
            //check target line is right,then calc next line
            boolean success = operateLastLine ?
                    check(board.table[lineIndex], board.table[lineIndex + 1]) : check(board.table[lineIndex]);
            if(success){
                if(operateLastLine || calcLine(board, lineIndex + 1)){
                    if(operateLastLine){
                        this.operationBoard = board;
                    }
                    result[lineIndex + 1] = internalX;
                    return true;
                }
            }
            internalX++;
            board = operationBoard.clone();

            operate(board, internalX, lineIndex + 1);
        }
        return false;
    }

    private boolean check(int... lines) {
        for (int i = 0; i < lines.length; i++) {
            int line = lines[i] & (lineMax - 1);
            Logger.print("calc line status:" + Integer.toBinaryString(line));
            if(line != 0){
                return false;
            }
        }
        return true;
    }

    public boolean operate(Board board, int operateLine, int lineIndex) {
        Logger.printWarn("calc line binary string:" + Integer.toBinaryString(operateLine) + ",line:" + lineIndex);
        for (int i = 0; i < board.shorterSide; i++) {
            int mark = 1 << i;
            if((operateLine & mark) > 0){
                if (lineIndex >= board.table.length) {
                    return false;
                }
                Position position = new Position();
                position.x = i;
                position.y = lineIndex;
                Logger.print("operate position:" + position);
                ruleOperator.execute(board, position);
            }
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return true;
    }

    public void printTable(int[] table){
        Logger.printWarn("print table:");
        for (int i = 0; i < table.length; i++) {
            String lineText = Integer.toBinaryString(table[i] & (lineMax - 1));
            System.out.println(String.format("%0" + rawBoard.shorterSide + "d", Integer.parseInt(lineText)));
        }
    }
}
